package common

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"reflect"
	"regexp"
	"strconv"
)

func AutoHandler(controller interface{}, action string) gin.HandlerFunc {
	t := reflect.TypeOf(controller)
	v := reflect.ValueOf(controller).MethodByName(action)
	vt := v.Type()
	if !v.IsValid() {
		panic(errors.New(fmt.Sprintf("register route failed: method[%s] in controller[%s] is not exists", action, t.String())))
	}

	if v.Kind() != reflect.Func {
		panic(errors.New(fmt.Sprintf("register route failed: method[%s] in controller[%s] is not a function", action, t.String())))
	}

	return func(context *gin.Context) {
		matches := regexp.MustCompile(`:(\w+)`).FindAllStringSubmatch(context.FullPath(), -1)
		p := len(matches) - 1

		ni := vt.NumIn() // Count inbound parameters
		parameters := make([]reflect.Value, 0)
		parameters = append(parameters, reflect.ValueOf(context))
		for i := 1; i < ni; i++ {
			if vt.In(i).Kind() == reflect.Ptr || vt.In(i).Kind() == reflect.Struct {
				pt := vt.In(i)
				if vt.In(i).Kind() == reflect.Ptr {
					pt = pt.Elem() // 指针需要取地址
				}
				pv := reflect.New(pt).Interface()
				if err := context.ShouldBindJSON(&pv); err != nil {
					BadRequest(context, fmt.Sprintf("bind value failed: %s", err))
					return
				}
				if vt.In(i).Kind() == reflect.Ptr {
					parameters = append(parameters, reflect.ValueOf(pv))
				} else {
					parameters = append(parameters, reflect.ValueOf(pv).Elem())
				}
			} else {
				log.Printf("paramter[idx: %d, type: %s] is string or numberic\n", i, vt.In(i))
				if p < 0 {
					BadRequest(context, fmt.Sprintf("invalid action[%s] params in controller[%s]", action, t.Name()))
					return
				}
				pv := context.Param(matches[p][1])
				switch vt.In(i).Kind() {
				case reflect.Int:
					if v, err := strconv.Atoi(pv); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(v))
					}
				case reflect.Int8:
					if v, err := strconv.ParseInt(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(int8(v)))
					}
				case reflect.Int16:
					if v, err := strconv.ParseInt(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(int16(v)))
					}
				case reflect.Int32:
					if v, err := strconv.ParseInt(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(int32(v)))
					}
				case reflect.Int64:
					if v, err := strconv.ParseInt(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(int64(v)))
					}
				case reflect.Uint:
					if v, err := strconv.ParseUint(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(uint(v)))
					}
				case reflect.Uint8:
					if v, err := strconv.ParseUint(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(uint8(v)))
					}
				case reflect.Uint16:
					if v, err := strconv.ParseUint(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(uint16(v)))
					}
				case reflect.Uint32:
					if v, err := strconv.ParseUint(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(uint32(v)))
					}
				case reflect.Uint64:
					if v, err := strconv.ParseUint(pv, 10, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(uint64(v)))
					}
				case reflect.Float32:
					if v, err := strconv.ParseFloat(pv, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(float32(v)))
					}
				case reflect.Float64:
					if v, err := strconv.ParseFloat(pv, 0); err != nil {
						panic(errors.New(fmt.Sprintf("bind value failed: %s", err)))
					} else {
						parameters = append(parameters, reflect.ValueOf(v))
					}
				case reflect.String:
					parameters = append(parameters, reflect.ValueOf(v))
				}
			}
		}
		v.Call(parameters)
	}
}
